/* eslint-disable no-mixed-spaces-and-tabs */
const EventEmitter = require('events');
import Logger from '../base/logger';
import io from './socket.io';
import { addEventListener, dispatchEvent, removeEventListener } from '../base/event';
import { IvcsError } from '../base/error';
import { uploadFile } from './api';
import { isJsonString, removeObjectWithId } from './tool';
import { ivcsErrorCodeEnum } from '../base/const';
import https from 'https';

const URL = require('url');
// https.globalAgent.options.rejectUnauthorized = false;
import { UrlParse } from '../base/urlParse';
// import {
//     StringDecoder
// } from 'string_decoder';
const errorCode = {
	refreshTicketFailed: 100,
	resumeConnectionFailed: 101,
	ticketIsEmpty: 102,
	ccsSocketIoDisconnected: 103,
	socketIoDrop: 104,
	socketIoReconnectFailed: 105,
	socketIoConnectError: 106,
};

const ccsEvent = {
	refreshTicketFailed: 'refreshTicketFailed',
	resumeConnectionFailed: 'resumeConnectionFailed',
	socketIoDisconnected: 'ccs_socket_disConnected',
	socketIoDrop: 'socketIoDrop',
	socketIoReconnectFailed: 'socketIoReconnectFailed',
};

const reconnectionAttempts = 13;

const waitMessageResponseTimes = 60000; // 发送消息后等待响应的时长，默认60秒，比重连时间长即可。

class CCSClient extends EventEmitter {
	constructor() {
		super();
		// ccs socketio 链接是否链接上。默认初始化是未连接上，或者重连失败。
		this.connected = false;
		this.isReconnected = false;
		this.members = [];
		this.setMaxListeners(1000000);
		this.isReconnecting = false; //是否正在重连中
		this.isListenOnMessage = false; // 是否监听session-messagesss
	}

	connect(url, token, success, failed) {
		Logger.info(`ivcs-js-sdk:ccsClient connect origin url,${url}`);
		let connectUrl = url;
		let index = url.indexOf('?');
		let query = {};
		if (index !== -1) {
			connectUrl = url.substr(0, index);
			let str = url.substr(index + 1);
			let strs = str.split('&');
			for (var i = 0; i < strs.length; i++) {
				let item = strs[i];
				let sIn = item.indexOf('=');
				if (sIn !== -1) {
					let key = item.substr(0, sIn);
					let value = item.substr(sIn + 1);
					query[key] = value;
				}
			}
		}
		const defaultOptions = {
			reconnect: true,
			secure: false,
			reconnectionAttempts: reconnectionAttempts,
			'force new connection': true,
			transports: ['websocket'],
			reconnectionDelayMax: 5000,
			reconnectionDelay: 1000,
			timeout: 3000,
			// [1,2,4,5,5,5,5,5,5,5,5,5,5,5,5,5]
		};
		if (url.indexOf('https://') == 0) {
			defaultOptions.agent = https.globalAgent;
		}
		Logger.info(`ivcs-js-sdk:ccsClient connect ccs options,${JSON.stringify(defaultOptions)}`);
		query.token = token;
		query.servicePath = '_ivcs_ccs';
		defaultOptions.query = query;
		let ccsUrl;
		if (URL && URL.parse) {
			ccsUrl = URL.parse(connectUrl);
			//work for Intranet ccs sio
			if (process.env.ivcsAgentMode == 1) {
				https.globalAgent.options.rejectUnauthorized = false;
			}
		} else if (URL && new URL(connectUrl)) {
			ccsUrl = new URL(connectUrl);
		} else {
			ccsUrl = new UrlParse(connectUrl).getUrlParams();
		}
		if (ccsUrl.pathname && ccsUrl.pathname !== '/') {
			defaultOptions.path = ccsUrl.pathname;
			ccsUrl.pathname = '';
			if (URL && URL.format) {
				connectUrl = URL.format(ccsUrl);
			} else {
				connectUrl = ccsUrl.toString();
			}
		}
		this.socket = io.connect(connectUrl, defaultOptions);
		this.socket.on('connect', data => {
			Logger.info(`ivcs-js-sdk:ccs-connect:${new Date().toISOString()},result:${data}`);
			this.socket.on('private-ping', () => {
				this.socket.emit('private-pong');
			});
			success();
			this.connected = true;
			!this.isListenOnMessage ? this.onMessage() : undefined; // 解决多次链接成功后，多次监听到receive-message.
			this.reconnectTimes = 0;
			this.emit('onConnectStatus', 'connected');
			this.isReconnecting = false;
		});
		this.socket.on('connect_error', function (data) {
			Logger.info(`ivcs-js-sdk:ccs-connect-error:${new Date().toISOString()},result:${data}`);
			// let err = {code: errorCode.socketIoConnectError, message: data, status:'connect_error'};
			// failed(err);
		});
		this.socket.on('connect_timeout', function (data) {
			Logger.info(`ivcs-js-sdk:ccs-connect_timeout:${new Date().toISOString()},result:${data}`);
			// let err = {code: errorCode.socketIoConnectError, message: data, status:'connect_timeout'};
			// failed(err);
		});
		this.socket.on('error', function (data) {
			Logger.info(`ivcs-js-sdk:ccs-error:${new Date().toISOString()},result:${data}`);
			let err = { code: errorCode.socketIoConnectError, message: data, status: 'error' };
			failed(err);
		});
		this.socket.on('disconnect', reason => {
			Logger.info(`ivcs-js-sdk:ccs-disconnect:${new Date().toISOString()},reason:${reason}`);
			if (reason === 'io server disconnect' && this.connected) {
				// disconnect initiated by server. Manually reconnect
				this.socket.connect();
			}
			if (reason === 'io client disconnect') {
				// ccs socketio send 41, self disconnect()
			}
			this.connected = false;
		});
		this.socket.on('reconnect', function (data) {
			Logger.info(`ivcs-js-sdk:ccs-reconnect:${new Date().toISOString()},result:${data}`);
		});
		this.socket.on('reconnect_attempt', function (data) {
			Logger.info(`ivcs-js-sdk:ccs-reconnect_attempt:${new Date().toISOString()},result:${data}`);
			this.isReconnecting = true;
		});
		this.socket.on('reconnecting', reconnectTimes => {
			this.reconnectTimes++;
			Logger.info(`ivcs-js-sdk:ccs-reconnecting:${new Date().toISOString()},reconnectTime:${reconnectTimes}`);
			this.emit('onConnectStatus', 'reconnecting');
		});
		this.socket.on('reconnect_error', function (data) {
			Logger.info(`ivcs-js-sdk:ccs-reconnect_error:${new Date().toISOString()},result:${data}`);
		});
		this.socket.on('reconnect_failed', function (data) {
			Logger.info(`ivcs-js-sdk:ccs-reconnect_failed:${new Date().toISOString()},result:${data}`);
			this.connected = false;
			this.isReconnecting = false;
			this.emit('onConnectStatus', 'reconnect_failed');
			let err = { code: errorCode.socketIoConnectError, message: data, status: 'connect_reconnect_timeout_failed' };
			failed(err);
		});
	}

	onConnectStatus(ack) {
		this.on('onConnectStatus', status => {
			ack(status);
		});
	}

	disconnect() {
		this.socket.disconnect();
	}

	requestSession(data, ack) {
		this.send('request-session', data, (status, resp) => {
			if (status === 'ok') {
				this.clientId = resp.clientId;
			}
			ack(status, resp);
		});
	}

	/**
	 * 获取会话中的成员
	 * @param {object} data
	 * @param {function} ack
	 */
	getSessionMembers(data = null, ack) {
		this.send('session-members', data, (status, resp) => {
			ack(status, resp);
		});
	}

	/**
	 * 媒体切换
	 * @param {object} data {media:'im','aduio,'video'}
	 * @param {function} ack {status:'ok',data:{roomToken,roomId}}
	 */
	requestMediaSwitch(data, ack) {
		this.send('request-media-switch', data, (status, resp) => {
			ack(status, resp);
		});
	}

	/**
	 * 监听 呼叫坐席,坐席接听状态回调信息
	 * @param {function} ack
	 */
	onInvitationProgress(ack) {
		this.on('invitation-progress', ack);
		return ack;
	}

	/**
	 * 监听媒体切换事件
	 * @param {function} ack
	 * {
		  "type": "media-switch",
		  "from": "system",
		  "to": ....,
		  "data": {
			 status: "upgrade",
			 type: "im" | "video" | "audio",
			 room: ...,
			 roomToken: ...,
			 at: ...
		  }
		}
	 */
	onMediaSwitch(ack) {
		this.on('media-switch', ack);
		return ack;
	}

	/**
	 * 取消监听 呼叫坐席,坐席接听状态回调信息
	 * @param {function} listener
	 */
	offInvitationProgress(listener) {
		this.off('invitation-progress', listener);
	}

	/**
	 * 在当前会话踢出某一个人
	 * @param {*} clientId
	 * @param {*} ack
	 */
	dropClient(clientId, ack) {
		this.send(
			'drop-client',
			{
				clientId: clientId,
			},
			(status, resp) => {
				ack(status, resp);
			}
		);
	}

	joinSession(data, ack) {
		this.send('join-session', data, (status, resp) => {
			if (status === 'ok') {
				this.clientId = resp.clientId;
			}
			ack(status, resp);
		});
	}

	onJoinedSession(ack) {
		this.on('peer-status:joined', ack);
	}

	/**
	 * 监听session中client重连成功
	 * @param {function} ack  返回client对象信息 
	 *  data:{
		  id: client.id,
		  status: 'reconnecting',
		  role: client.role,
		  user: client.user,
		  at: moment().toISOString()
		}
	 */
	onClientResumeConnectStatus(ack) {
		this.on('peer-status:resume-connect', ack);
	}

	/**
	 * 监听session中client正在重连中
	 * @param {function} ack 返回client对象信息
	 *  data:{
		  id: client.id,
		  status: 'reconnecting',
		  role: client.role,
		  user: client.user,
		  at: moment().toISOString()
		}
	 */
	onClientReconnectingStatus(ack) {
		this.on('peer-status:reconnecting', ack);
	}

	/**
	 * 挂断会话
	 * @param {object} data 挂断会话的原因,格式 {reason:"异常挂断"}
	 * @param {function} ack 回调
	 */
	quitSession(data, ack) {
		this.send('quit-session', data, (status, resp) => {
			if (status === 'ok') {
				this.clientId = null;
			}
			ack(status, resp);
		});
	}

	onQuitSession(ack) {
		this.on('peer-status:quit', ack);
	}

	/**
	 * 坐席离线
	 * @param {object} data { roomId：房间id, participantId: participantId, agentId: 登录系统唯一id }
	 * @param {function} ack  回调
	 */
	offline(data, ack) {
		this.send('offline', data, function (status, resp) {
			ack(status, resp);
		});
	}
	sendMessage(type, data, to, ack) {
		if (this.clientId) {
			let message = {
				type: type,
				id: Math.round(Math.random() * 1000000000000000000) + '',
				from: this.clientId,
				to: to,
				data: data,
			};
			this.send('session-message', message, (status, resp) => {
				if (status !== 'ok') {
					ack && ack(status, resp, message.id);
				} else {
					if (type === 'peer-cmd' && (data?.cmd === 'co-browsing' || data?.cmd === 'browsing-mark')) {
						ack && ack(status, resp, message.id);
						return;
					}
					if (type === 'peer-response' && (data?.cmd === 'co-browsing' || data?.cmd === 'browsing-mark')) {
						ack && ack(status, resp, message.id);
						return;
					}
					// ready-to-talk消息
					if (type === 'peer-status' && data?.status === 'ready-to-talk') {
						ack && ack(status, resp, message.id);
						return;
					}
					// 无需其他人响应的消息，则发送成功
					if (type === 'ack' || type === 'real-time-quality' || type === 'start-recording' || type === 'stop-recording') {
						ack && ack(status, resp, message.id);
						return;
					}
					// 未收到对方ack消息，默认5秒则认为没有收到消息
					const ackTimeOutTimes = type !== 'text' ? 5000 : 60000; // 文本消息1分钟
					let timer = setTimeout(() => {
						ack && ack('ack-timeout', 'receive ack message timeout', message.id);
						ack = null;
					}, ackTimeOutTimes);
					// {"type":"ack","id":"882804123256988500","from":"350957128162749500","to":"496261430983743400","data":{"id":"1775367872368650"},"fromUser":{"id":7,"name":"001","avatar":"https://ivcs-test-k8s-azure.zealcomm.cn/ivcs/upload/bce32a44b596ece8e8bb2521035e8834.png","isRobot":false,"role":"customer","customerRole":"访客"},"createdAt":"2023-02-27T11:02:23.821Z"}
					this.onMessageAck(ackData => {
						if (ackData?.data?.id === message.id) {
							ack && ack(status, resp, message.id);
							clearTimeout(timer);
							timer = null;
						}
					});
				}
			});
		} else {
			Logger.warning(`ivcs-js-sdk:clientId is null,don't in session`, new Date().toISOString());
			ack('error', 'clientId is null');
		}
	}

	/**
	 * 发送消息。设置from to
	 * @param {string} type
	 * @param {object} data
	 * @param {string} from
	 * @param {string} to
	 * @param {function} ack
	 */
	sendMessageFromTo(type, data, from, to, ack) {
		let fromClientId = from ? from : this.clientId;
		let message = {
			type: type,
			id: Math.round(Math.random() * 1000000000000000000) + '',
			from: fromClientId,
			to: to,
			data: data,
		};
		this.send('session-message', message, (status, resp) => {
			ack(status, resp, message.id);
		});
	}

	confirmReceiveMessage(messageId, toWhom) {
		let data = {
			id: messageId,
		};
		this.sendMessage('ack', data, toWhom, (status, resp) => {
			if (status === 'ok') {
				Logger.info(`ivcs-js-sdk:send ack -- ${messageId} ok`);
			} else {
				Logger.warning(`ivcs-js-sdk:send ack -- ${messageId} error`, resp);
			}
		});
	}

	/**
	 * ccs-收到系统消息or对方发送的消息
	 */
	onMessage() {
		this.receive('session-message', (msg, onResponse) => {
			let msgData = {
				type: msg.type,
				id: msg.id,
				from: msg.from,
				data: msg.data,
				createdAt: msg.createdAt,
				fromUser: msg.fromUser,
				to: msg.to,
			};
			if (msgData.type.includes('peer-cmd')) {
				msgData.data = this.setPeerCmdData(msgData.data);
				this.emit(`peer-cmd:${msgData.data['cmd']}`, msgData);
			} else if (msgData.type.includes('peer-status')) {
				this.emit(`peer-status:${msgData.data['status']}`, msgData);
				onResponse('ok');
			} else if (msgData.type.includes('peer-cancel')) {
				this.emit(`peer-cancel:${msgData.data['cmd']}`, msgData);
			} else if (msgData.type.includes('peer-response')) {
				msgData.data = this.setPeerResponseData(msgData.data);
				this.emit(`peer-response:${msgData.data['cmd']}`, msgData);
			} else {
				this.emit(`${[msgData.type]}`, msgData);
			}
			if (msgData.type !== 'ack' && msgData.from !== 'system') {
				this.confirmReceiveMessage(msg.id, msg.from);
			}
			if (msgData.type === 'invitation-progress') {
				// 呼叫进度响应后端ok。未响应则后端会重发。
				onResponse('ok');
			}
		});
		this.isListenOnMessage = true;
	}

	/**
	 * 设置资源文件的内外网替换地址
	 * @param {string} resourceOriginUrlDomain 'ivcs-nginx'
	 * @param {string} resourceReplaceUrlDomain '公网-domain'，	'内网-domain'
	 */
	setReplaceOrigin(resourceOriginUrlDomain, resourceReplaceUrlDomain) {
		this.resourceOriginUrlDomain = resourceOriginUrlDomain;
		this.resourceReplaceUrlDomain = resourceReplaceUrlDomain;
	}

	/**
	 * 设置响应的数据
	 * @param {object} data
	 */
	setPeerResponseData(data) {
		if (!this.resourceOriginUrlDomain || !this.resourceReplaceUrlDomain) {
			return data;
		}
		// 截图
		if (data?.cmd === 'get-screenshot') {
			if (data?.data?.url && data?.data?.url?.indexOf(this.resourceOriginUrlDomain) !== -1) {
				data.data.url = data.data.url.replace(this.resourceOriginUrlDomain, this.resourceReplaceUrlDomain);
			}
		}
		// 短视频采集
		if (data?.cmd === 'collect-short-video') {
			if (data?.data?.url && data?.data?.url?.indexOf(this.resourceOriginUrlDomain) !== -1) {
				data.data.url = data.data.url.replace(this.resourceOriginUrlDomain, this.resourceReplaceUrlDomain);
			}
		}
		// 手写签名
		if (data?.cmd === 'get-handwriting') {
			if (data?.data?.url && data?.data?.url?.indexOf(this.resourceOriginUrlDomain) !== -1) {
				data.data.url = data.data.url.replace(this.resourceOriginUrlDomain, this.resourceReplaceUrlDomain);
			}
			if (data?.data?.pic && data?.data?.pic?.indexOf(this.resourceOriginUrlDomain) !== -1) {
				data.data.pic = data.data.pic.replace(this.resourceOriginUrlDomain, this.resourceReplaceUrlDomain);
			}
		}
		// 截屏
		if (data?.cmd === 'get-screencapture') {
			if (data?.data?.url && data?.data?.url?.indexOf(this.resourceOriginUrlDomain) !== -1) {
				data.data.url = data.data.url.replace(this.resourceOriginUrlDomain, this.resourceReplaceUrlDomain);
			}
		}
		return data;
	}

	/**
	 * 设置指令的数据
	 * @param {object} data
	 */
	setPeerCmdData(data) {
		if (!this.resourceOriginUrlDomain || !this.resourceReplaceUrlDomain) {
			return data;
		}
		// 推送pdf
		if (data?.cmd === 'view') {
			if (data?.data?.type && data?.data?.type === 'paper') {
				if (data?.data?.url && data?.data?.url?.indexOf(this.resourceOriginUrlDomain) !== -1) {
					data.data.url = data.data.url.replace(this.resourceOriginUrlDomain, this.resourceReplaceUrlDomain);
				}
			}
		}
		// 人证对比
		if (data?.cmd === 'face-idcard-validate') {
			if (data?.data?.imageUrl && data?.data?.imageUrl?.indexOf(this.resourceOriginUrlDomain) !== -1) {
				data.data.imageUrl = data.data.imageUrl.replace(this.resourceOriginUrlDomain, this.resourceReplaceUrlDomain);
			}
		}
		// 人脸对比
		if (data?.cmd === 'face-recognition') {
			if (data?.data?.imageUrl && data?.data?.imageUrl?.indexOf(this.resourceOriginUrlDomain) !== -1) {
				data.data.imageUrl = data.data.imageUrl.replace(this.resourceOriginUrlDomain, this.resourceReplaceUrlDomain);
			}
		}
		return data;
	}

	/**
	 * 收到im升级
	 */
	onUpgradeAck(ack) {
		this.on(`on-upgrade`, ack);
	}

	/**
	 * 监听发送的消息是否对方已经接收到！
	 * @param {function} ack
	 */
	onMessageAck(ack) {
		this.on(`ack`, ack);
	}

	/**
	 * 监听sessionId发生变化！
	 * @param {function} ack {sessionId}
	 */
	onSessionIdChange(ack) {
		this.on(`sessionId-change`, ack);
	}

	/**
	 * 正在转接给其他坐席。告知访客正在转接中
	 * @param {string} toWhom 发送给谁 clientId具体的用户 'customers'= 访客;'agents'= 坐席;'others'= 除了自己的所有人; 'all' = 所有人;
	 * @param {function} ack
	 * @param {object} transferInfo 转接的相关信息,业务场景id/坐席id/业务场景名称等
	 */
	holdOn(toWhom, ack, transferInfo) {
		let data = {
			status: 'hold-on',
			at: new Date().toISOString(),
			transferInfo: transferInfo,
		};
		this.sendMessage('peer-status', data, toWhom, (status, resp, msgId) => {
			ack(status, resp, msgId);
		});
	}

	onHoldOn(ack) {
		this.on(`peer-status:${['hold-on']}`, ack);
	}

	/**
	 * 发送zms房间事件
	 * @param {object} data  { event: ""} eg:{event:'publish-success'}
	 * @param {function} ack  消息回调
	 */
	sendZmsRoomEvent(data, ack) {
		this.send('room-event', data, (status, resp, msgId) => {
			ack(status, resp, msgId);
		});
	}

	/**
	 * 发送转人工原因
	 * @param {object} data 转人工的原因 例如:{reason:"转人工原因"}
	 * @param {string} toWhom 告知的角色，customer,agents,或者具体的clientId
	 * @param {function} ack 消息回调
	 */
	sendTransferReason(data, toWhom, ack) {
		this.sendMessage('transfer', data, toWhom, (status, resp, msgId) => {
			ack(status, resp, msgId);
		});
	}

	/**
	 * 收到转人工原因
	 * @param {function} ack 返回具体的原因数据
	 */
	onTransferReason(ack) {
		this.on('transfer', ack);
	}

	/**
	 * 发送人工工作流节点完成情况
	 * @param {object} data {humanflowId:(人工工作流ID),step:(人工工作流执行步骤ID),at:(时间)}
	 * @param {string} toWhom 发送给谁 clientId具体的用户 'customers'= 访客;'agents'= 坐席;'others'= 除了自己的所有人; 'all' = 所有人;
	 * @param {function} ack 消息回调
	 */
	sendHumanStepDetail(data, toWhom, ack) {
		this.sendMessage('human-step', data, toWhom, (status, resp, msgId) => {
			ack(status, resp, msgId);
		});
	}

	/**
	 * 收到人工工作流节点完成情况
	 * @param {function} ack 返回具体的节点完成数据  {humanflowId:(人工工作流ID),step:(人工工作流执行步骤ID),at:(时间)}
	 */
	onHumanStepDetail(ack) {
		this.on('human-step', ack);
	}

	/**
	 * 收到发送业务指令消息，进行业务指令留痕
	 * @param {function} ack 返回具体的数据
	 */
	onSyncMessage(ack) {
		this.on('sync-message', ack);
	}

	/**
	 * 监听实时质检结果返回
	 * @param {function} ack 返回具体的数据
	 */
	onRealTimeQuality(ack) {
		this.on('real-time-quality', ack);
	}

	/**
	 * 告知机器人的播收音方式
	 * @param {object} data {type:"talk-only" //talk-only | hear-only | talk-hear}
	 * @param {string} toWhom 发送给谁 clientId具体的用户 'customers'= 访客;'agents'= 坐席;'others'= 除了自己的所有人; 'all' = 所有人;
	 * @param {function} ack
	 */
	sendCallDirection(data, toWhom, ack) {
		this.sendMessage('call-direction', data, toWhom, (status, resp, msgId) => {
			ack(status, resp, msgId);
		});
	}

	/**
	 * 收到机器人的播收音方式，决定是否需要订阅机器人
	 * @param {function} ack
	 */
	onCallDirection(ack) {
		this.on('call-direction', ack);
	}

	startTyping(toWhom, ack) {
		let data = {
			status: 'start-typing',
			at: new Date().toISOString(),
		};
		this.sendMessage('peer-status', data, toWhom, function (status, resp, msgId) {
			ack(status, resp, msgId);
		});
	}

	onStartTyping(ack) {
		this.on('peer-status:start-typing', ack);
	}

	stopTyping(toWhom, ack) {
		let data = {
			status: 'stop-typing',
			at: new Date().toISOString(),
		};
		this.sendMessage('peer-status', data, toWhom, function (status, resp, msgId) {
			ack(status, resp, msgId);
		});
	}

	onStopTyping(ack) {
		this.on('peer-status:stop-typing', ack);
	}

	readyToTalk(participantId, user, toWhom, ack) {
		let data = {
			status: 'ready-to-talk',
			at: new Date().toISOString(),
			participantId: participantId,
			user: user,
		};
		this.sendMessage('peer-status', data, toWhom, function (status, resp, msgId) {
			ack(status, resp, msgId);
		});
	}

	onReadyToTalk(ack) {
		this.on('peer-status:ready-to-talk', ack);
	}

	/**
	 * 开始录制，录像类型： 系统录像、坐席录屏、区域录屏；目前只做系统留痕
	 * @param {object} data {type:"system", "agent-screen", "regional-screen", }
	 * @param {string} toWhom 发送给谁 clientId具体的用户 'customers'= 访客;'agents'= 坐席;'others'= 除了自己的所有人; 'all' = 所有人;
	 * @param {function} ack
	 */
	startRecording(data, toWhom, ack) {
		this.sendMessage('start-recording', data, toWhom, function (status, resp, msgId) {
			ack(status, resp, msgId);
		});
	}

	/**
	 * 停止录制，录像类型： 系统录像、坐席录屏、区域录屏；目前只做系统留痕
	 * @param {object} data {type:"system", "agent-screen", "regional-screen", }
	 * @param {string} toWhom 发送给谁 clientId具体的用户 'customers'= 访客;'agents'= 坐席;'others'= 除了自己的所有人; 'all' = 所有人;
	 * @param {function} ack
	 */
	stopRecording(data, toWhom, ack) {
		this.sendMessage('stop-recording', data, toWhom, function (status, resp, msgId) {
			ack(status, resp, msgId);
		});
	}

	/**
	 * 监听系统双录 开启和暂停事件
	 * @param {function} ack  event:'system-record',status:'start'|'stop',recordId:''
	 */
	onSystemRecordEvent(ack) {
		this.on('start-recording', data => {
			if (data?.data?.type === 'system' || data?.data?.type === 'system-recording') {
				ack({
					event: 'system-record',
					status: 'start',
					recordId: data?.data?.recordId,
					result: data?.data?.result, // success error
				});
			}
		});
		this.on('stop-recording', data => {
			if (data?.data?.type === 'system') {
				ack({
					event: 'system-record',
					status: 'stop',
					recordId: data?.data?.recordId,
				});
			}
		});
	}

	/**
	 * 访客加入room
	 * @param {string} participantId
	 * @param {object} user
	 * @param {string} toWhom 发送给谁 clientId具体的用户 'customers'= 访客;'agents'= 坐席;'others'= 除了自己的所有人; 'all' = 所有人;
	 * @param {function} ack
	 */
	customerJoinRoom(participantId, user, toWhom, ack) {
		let data = {
			status: 'customer-join-room',
			at: new Date().toISOString(),
			participantId: participantId,
			user: user,
		};
		this.sendMessage('peer-status', data, toWhom, function (status, resp, msgId) {
			ack(status, resp, msgId);
		});
	}

	/**
	 * 访客加入房间
	 * @param {function} ack
	 */
	onCustomerJoin(ack) {
		this.on('peer-status:customer-join-room', ack);
	}

	onDisconnect(ack) {
		this.receive('disconnect', reason => {
			if (this.reconnectTimes >= reconnectionAttempts) {
				dispatchEvent(ccsEvent.socketIoDisconnected, errorCode.ccsSocketIoDisconnected, 'ccs socket io disconnected');
				ack(reason);
			}
		});
	}

	onReconnectFailed(ack) {
		this.receive('reconnect_failed', reason => {
			if (this.reconnectTimes >= reconnectionAttempts) {
				dispatchEvent(ccsEvent.socketIoReconnectFailed, errorCode.socketIoReconnectFailed, 'ccs socket io reconnect_failed');
				ack(reason);
			}
		});
	}

	receive(eventName, ack) {
		if (this.socket) {
			this.socket.on(eventName, ack);
		} else {
			Logger.error('ivcs-js-sdk:There is no socket connect');
			ack('socket on event error,', eventName);
		}
	}

	send(eventName, data, ack) {
		if (!this.connected || this.isReconnecting) {
			Logger.error(`ivcs-js-sdk:ccs socketio disconnected with event:${eventName},data:${data}`);
			ack('disconnected', `ccs socketio disconnected or reconnecting with event:${eventName},data:${data}`);
			return;
		}
		if (this.socket && this.socket.connected) {
			//socketio正在重连中的时候去发送指令，指令会丢失
			// const waitResultTime = 60 * 1000;
			let isReconnectingInterval = setTimeout(() => {
				let message = isJsonString(data) ? `send message with event:${eventName} timeout,data:${JSON.stringify(data)}` : `send message with event:${eventName} timeout,data:${data}`;
				ack('timeout', message);
			}, waitMessageResponseTimes);
			if (data) {
				this.socket.emit(eventName, data, (status, res) => {
					clearTimeout(isReconnectingInterval);
					isReconnectingInterval = null;
					ack(status, res);
				});
			} else {
				this.socket.emit(eventName, (status, res) => {
					clearTimeout(isReconnectingInterval);
					isReconnectingInterval = null;
					ack(status, res);
				});
			}
		} else {
			Logger.error('ivcs-js-sdk:There is no socket connect while send message with Name', eventName, '&data,', data);
			ack('error', 'There is no socket connect while send message with Name', eventName, '&data,', data);
		}
	}

	onConnect(ack) {
		this.receive('connect', () => {
			Logger.info('ivcs-js-sdk:ccs connect at,', new Date().toISOString());
			this.reconnectTimes = 0;
			this.connected = true;
			ack();
		});
	}

	refreshTicket(ack) {
		this.send('refresh-ticket', null, function (status, resp) {
			ack(status, resp);
		});
	}

	resumeConnection(ticket, ack) {
		this.send('resume-connection', ticket, (status, resp) => {
			// clientId,ticket,media,members
			Logger.info(`ivcs-js-sdk:ccs resumeConnection status,${status},response:${resp},time:${new Date().toISOString()}`);
			ack(status, resp);
			if (status != 'ok') {
				this.emit('resume-connect-error', resp);
			} else {
				this.emit('resume-connect-success', resp);
			}
		});
	}

	/**
	 * ccs resume-connection-error
	 * @param {function} ack
	 */
	resumeConnectionError(ack) {
		this.on('resume-connect-error', reason => {
			ack(reason);
		});
	}

	/**
	 * ccs resume-connection-success
	 * @param {function} ack
	 */
	resumeConnectionSuccess(ack) {
		this.on('resume-connect-success', data => {
			ack(data);
		});
	}

	onError(ack) {
		this.receive('error', data => {
			ack(data);
		});
	}

	onDrop(ack) {
		this.receive('drop', data => {
			ack(data);
		});
	}

	invite(data, ack) {
		this.send('invite', data, (status, resp) => {
			ack(status, resp);
		});
	}

	/**
	 * 取消所有的invite
	 * @param {*} parameter 默认为空，不需要参数
	 * @param {*} ack
	 */
	cancelInvitations(parameter = undefined, ack) {
		this.send('cancel-invitations', parameter, (status, resp) => {
			ack(status, resp);
		});
	}

	photoGuideBox(askForPermission = false, toWhom, ack) {
		let data = {
			cmd: 'photo_guidebox',
			askForPermission: askForPermission,
		};
		this.sendMessage('peer-cmd', data, toWhom, (status, resp, msgId) => {
			ack(status, resp, msgId);
		});
	}

	onPhotoGuideBox(ack) {
		this.on('peer-cmd:photo_guidebox', ack);
	}

	/**
	 * 推送表单
	 * @param {object} data 推送的表单详情,example :{formId:123}
	 * @param {string} to 发送给谁 clientId具体的用户 'customers'= 访客;'agents'= 坐席;'others'= 除了自己的所有人; 'all' = 所有人;
	 * @param {function} ack 发送的结果回调 {status,resp,msgId} status == 'ok'表示发送成功
	 */
	sendForm(data, to, ack) {
		let msg = {
			cmd: 'send-form',
			data: data,
		};
		this.sendMessage('peer-cmd', msg, to, (status, resp, msgId) => {
			ack(status, resp, msgId);
		});
	}

	/**
	 * 接收推送的表单内容
	 * @param {function} ack 返回对方推送的表单内容 example:{cmd:'push-form',data:{formId:123}}
	 */
	onFormData(ack) {
		this.on('peer-cmd:send-form', ack);
	}

	/**
	 * 发送静默等待，目前适用机器人发送静默等待模版
	 * @param {object} data  推送的内容,example :{time:123}
	 * @param {string} to 发送给谁 clientId具体的用户 'customers'= 访客;'agents'= 坐席;'others'= 除了自己的所有人; 'all' = 所有人;
	 * @param {function} ack ack 发送的结果回调 {status,resp,msgId} status == 'ok'表示发送成功
	 */
	sendSilentWaiting(data, to, ack) {
		let msg = {
			status: 'silent-waiting',
			at: new Date().toISOString(),
			data: data,
		};
		this.sendMessage('peer-status', msg, to, function (status, resp, msgId) {
			ack(status, resp, msgId);
		});
	}

	/**
	 * 接收到静默等待的消息
	 * @param {function} ack
	 */
	onSilentWaiting(ack) {
		this.on(`peer-status:silent-waiting`, ack);
	}

	/**
	 * 发送选项判断
	 * @param {object} data 推送的表单详情,example :{option:123}
	 * @param {string} to 发送给谁 clientId具体的用户 'customers'= 访客;'agents'= 坐席;'others'= 除了自己的所有人; 'all' = 所有人;
	 * @param {function} ack 发送的结果回调 {status,resp,msgId} status == 'ok'表示发送成功
	 */
	sendOptionJudgment(data, to, ack) {
		let msg = {
			cmd: 'option-judgment',
			data: data,
		};
		this.sendMessage('peer-cmd', msg, to, (status, resp, msgId) => {
			ack(status, resp, msgId);
		});
	}

	/**
	 * 接收选项判断的内容
	 * @param {function} ack 返回对方推送的内容
	 */
	onOptionJudgment(ack) {
		this.on('peer-cmd:option-judgment', ack);
	}

	/**
	 * 发送汇总确认模版
	 * @param {object|string} data 发送汇总确认的模版的图片地址
	 * @param {string} to 发送给谁 clientId具体的用户 'customers'= 访客;'agents'= 坐席;'others'= 除了自己的所有人; 'all' = 所有人;
	 * @param {function} ack 发送的结果回调 {status,resp,msgId} status == 'ok'表示发送成功
	 */
	sendSummaryConfirm(data, to, ack) {
		let msg = {
			cmd: 'summary-confirm',
			data: data,
		};
		this.sendMessage('peer-cmd', msg, to, (status, resp, msgId) => {
			ack(status, resp, msgId);
		});
	}

	/**
	 * 接收推送的汇总模版的所有内容
	 * @param {function} ack 返回对方推送的汇总模版的详情
	 */
	onSummaryConfirm(ack) {
		this.on('peer-cmd:summary-confirm', ack);
	}

	/**
	 * 推送查阅等待消息，常用于访客扫码后，AI推送查阅等待，等待用户确认查看扫码后的内容
	 * @param {object} data 推送的表单详情,example :{type:'scan-code',code:'SN1212312313'}
	 * @param {string} to 发送给谁 clientId具体的用户 'customers'= 访客;'agents'= 坐席;'others'= 除了自己的所有人; 'all' = 所有人;
	 * @param {function} ack 发送的结果回调 {status,resp,msgId} status == 'ok'表示发送成功
	 */
	waitingRead(data, to, ack) {
		let msg = {
			cmd: 'waiting-read',
			data: data,
		};
		this.sendMessage('peer-cmd', msg, to, (status, resp, msgId) => {
			ack(status, resp, msgId);
		});
	}

	/**
	 * 接收查阅等待消息内容
	 * @param {function} ack 返回对方推送的表单内容 example:{cmd:'waiting-read',data:{type:'scan-code',code:'SN1212312313'}}
	 */
	onWaitingRead(ack) {
		this.on('peer-cmd:waiting-read', ack);
	}

	/**
	 * 请求获取地理位置
	 * @param {boolean} askForPermission 是否需要对方权限，默认是false
	 * @param {string} toWhom 发送给谁 clientId具体的用户 'customers' = 访客;'agents' = 坐席;'others' = 除了自己的所有人; 'all' = 所有人;
	 * @param {function} ack 发送结果回调
	 */
	requestLocation(askForPermission = false, toWhom, ack) {
		let data = {
			cmd: 'request-location',
			askForPermission: askForPermission,
		};
		this.sendMessage('peer-cmd', data, toWhom, (status, resp, msgId) => {
			ack(status, resp, msgId);
		});
	}

	/**
	 * 收到邀请获取地理位置的指令，常用于访客端,用来接受坐席推送的获取地理位置的指令
	 * @param {function} ack 回调，表明收到获取地理位置指令
	 */
	onRequestLocation(ack) {
		this.on('peer-cmd:request-location', ack);
	}

	/**
	 * 取消已经发送的指令
	 * @param {string|object} instructionData  instructionName 指令名 | {instructionName:'',data:''}
	 * @param {string} toWhom 发送给谁 clientId具体的用户 'customers' = 访客;'agents' = 坐席;'others' = 除了自己的所有人; 'all' = 所有人;
	 * @param {function} ack 发送结果回调
	 */
	cancelInstruction(instructionData, toWhom, ack) {
		let data = {
			cmd: 'cancel-instruction',
			type: instructionData,
		};
		if (typeof instructionData != 'string') {
			data.type = instructionData.instructionName;
			data.data = instructionData;
		}
		this.sendMessage('peer-cmd', data, toWhom, (status, resp, msgId) => {
			ack(status, resp, msgId);
		});
	}

	/**
	 * 收到取消已经发送的事件指令，常用于访客端,用来接受坐席取消相关指令
	 * @param {function} ack 回调，表明收到取消事件指令
	 */
	onCancelInstruction(ack) {
		this.on('peer-cmd:cancel-instruction', ack);
	}

	/**
	 * 请求扫描二维码
	 * @param {boolean} askForPermission 是否需要对方权限，默认是false
	 * @param {string} toWhom 发送给谁 clientId具体的用户 'customers' = 访客;'agents' = 坐席;'others' = 除了自己的所有人; 'all' = 所有人;
	 * @param {function} ack 发送结果回调
	 */
	scanCode(askForPermission = false, toWhom, ack) {
		let data = {
			cmd: 'scan-code',
			askForPermission: askForPermission,
		};
		this.sendMessage('peer-cmd', data, toWhom, (status, resp, msgId) => {
			ack(status, resp, msgId);
		});
	}

	/**
	 * 收到获取扫描二维码的指令，常用于访客端,用来接受坐席推送的扫描二维码指令
	 * @param {function} ack 回调，表明收到扫描二维码指令
	 */
	onScanCode(ack) {
		this.on('peer-cmd:scan-code', ack);
	}

	requestScreenShare(askForPermission = false, toWhom, ack) {
		let data = {
			cmd: 'view-screen',
			askForPermission: askForPermission,
		};
		this.sendMessage('peer-cmd', data, toWhom, (status, resp, msgId) => {
			ack(status, resp, msgId);
		});
	}

	onRequestScreenShare(ack) {
		this.on('peer-cmd:view-screen', ack);
	}

	releaseScreen(askForPermission = false, toWhom, ack) {
		let data = {
			cmd: 'release-screen',
			askForPermission: askForPermission,
		};
		this.sendMessage('peer-cmd', data, toWhom, (status, resp, msgId) => {
			ack(status, resp, msgId);
		});
	}

	onReleaseScreen(ack) {
		this.on('peer-cmd:release-screen', ack);
	}

	sendDeviceScreenSize(data, toWhom, ack) {
		this.sendMessage('screenSize', data, toWhom, (status, resp, msgId) => {
			ack(status, resp, msgId);
		});
	}

	onDeviceScreenSize(ack) {
		this.on('screenSize', ack);
	}

	markScreen(data, toWhom, ack) {
		this.sendMessage('markScreen', data, toWhom, (status, resp, msgId) => {
			ack(status, resp, msgId);
		});
	}

	onScreenPoint(ack) {
		this.on('markScreen', ack);
	}

	screenSnapshot(askForPermission = false, toWhom, ack) {
		let data = {
			cmd: 'screen-snapshot',
			askForPermission: askForPermission,
		};
		this.sendMessage('peer-cmd', data, toWhom, (status, resp, msgId) => {
			ack(status, resp, msgId);
		});
	}

	onScreenSnapshot(ack) {
		this.on('peer-cmd:screen-snapshot', ack);
	}

	// eslint-disable-next-line no-unused-vars
	handSign(askForPermission = false, data, toWhom, ack) {
		this.sendMessage('handwriting', data, toWhom, (status, resp, msgId) => {
			ack(status, resp, msgId);
		});
	}

	onHandSignature(ack) {
		this.on('handwriting', ack);
	}

	sendScreenMarkMessage(trajectoryData, toWhom, ack) {
		this.sendMessage('screenShareMark', trajectoryData, toWhom, (status, resp, msgId) => {
			ack(status, resp, msgId);
		});
	}

	onScreenMark(ack) {
		this.on('screenShareMark', ack);
	}

	/**
	 * 发生页面状态，隐藏/显示
	 * @param {object} data {status:'hidden'|'visiable'}
	 * @param {string} toWhom
	 * @param {function} ack
	 */
	sendPageStatus(data, toWhom, ack) {
		this.sendMessage('page-status', data, toWhom, (status, resp, msgId) => {
			ack(status, resp, msgId);
		});
	}

	/**
	 * 发送ai实时质检的结果
	 * @param {object} data
	 * @param {string} toWhom
	 * @param {function} ack
	 */
	sendRealTimeQuality(data, toWhom, ack) {
		this.sendMessage('real-time-quality', data, toWhom, (status, resp, msgId) => {
			ack(status, resp, msgId);
		});
	}

	/**
	 * 监听对方页面变化
	 * @param {function} ack
	 */
	onPageStatus(ack) {
		this.on('page-status', ack);
	}

	/**
	 * 会话中的变量发生变化，通知给会话中的用户
	 * @param {object} data {id(变量id),value(变量值),roleId(角色id),roleName(角色名字)}
	 * @param {string} toWhom 发送给谁 clientId具体的用户 'customers' = 访客;'agents' = 坐席;'others' = 除了自己的所有人; 'all' = 所有人;
	 * @param {function} ack
	 */
	sendVariableChange(data, toWhom, ack) {
		this.sendMessage('variable-change', data, toWhom, (status, resp, msgId) => {
			ack(status, resp, msgId);
		});
	}

	/**
	 * 监听会话中的变量发生变化
	 * @param {function} ack {id(变量id),value(变量值),roleId(角色id),roleName(角色名字)}
	 */
	onVariableChange(ack) {
		this.on('variable-change', ack);
	}

	/**
	 * 坐席完成会话小结内容，通知给会话中的其他坐席
	 * @param {object} data  {"bizSummaryId": 1,  //会话小结配置项ID "name": "字段名称", // 字段名称"type": "textarea", // 类型"data": {}, //选项配置内容 "value": {}, // 选项值 "isRequired": "Y"  // 是否必填  N}
	 * @param {string} toWhom 发送给谁 clientId具体的用户 'customers' = 访客;'agents' = 坐席;'others' = 除了自己的所有人; 'all' = 所有人;
	 * @param {function} ack
	 */
	syncSessionSummary(data, toWhom = 'agents', ack) {
		this.sendMessage('sync-session-summary', data, toWhom, (status, resp, msgId) => {
			ack(status, resp, msgId);
		});
	}

	/**
	 * 监听当前会话的会话小结的内容变化，其他坐席做的会话小结变更，都会通知到当前坐席
	 * @param {function} ack {"bizSummaryId": 1,  //会话小结配置项ID "name": "字段名称", // 字段名称"type": "textarea", // 类型"data": {}, //选项配置内容 "value": {}, // 选项值 "isRequired": "Y"  // 是否必填  N}
	 */
	onSyncSessionSummary(ack) {
		this.on('sync-session-summary', ack);
	}

	/**
	 * 机器人开始播报话术
	 * @param {string|object} data 播报的话术，暂定传递为空对象，{}
	 * @param {string} toWhom 发送给谁 clientId具体的用户 'customers' = 访客;'agents' = 坐席;'others' = 除了自己的所有人; 'all' = 所有人;
	 * @param {function} ack {status,resp,msgId}
	 */
	sendAiStartSpeak(data, toWhom, ack) {
		this.sendMessage('start-speaking', data, toWhom, (status, resp, msgId) => {
			ack(status, resp, msgId);
		});
	}

	/**
	 * 监听到机器人开始播报一句话术
	 * @param {function} ack {status,resp,msgId}
	 */
	onAiStartSpeak(ack) {
		this.on('start-speaking', ack);
	}

	/**
	 * 发送ocr识别结果
	 * @param {object} data 识别的结果
	 * @param {string} toWhom 发送给谁 clientId具体的用户 'customers' = 访客;'agents' = 坐席;'others' = 除了自己的所有人; 'all' = 所有人;
	 * @param {function} ack {status,resp,msgId}
	 */
	sendOcrRecord(data, toWhom, ack) {
		this.sendMessage('ocr', data, toWhom, (status, resp, msgId) => {
			ack(status, resp, msgId);
		});
	}

	/**
	 * 监听到ocr识别结果
	 * @param {function} ack {status,resp,msgId}
	 */
	onOcrRecord(ack) {
		this.on('ocr', ack);
	}

	/**
	 * 机器人播报话术结束
	 * @param {string｜object} data 播报的话术，暂定传递为空对象，{}
	 * @param {string} toWhom 发送给谁 clientId具体的用户 'customers' = 访客;'agents' = 坐席;'others' = 除了自己的所有人; 'all' = 所有人;
	 * @param {function} ack {status,resp,msgId}
	 */
	sendAiSpeakDone(data, toWhom, ack) {
		this.sendMessage('speaking-done', data, toWhom, (status, resp, msgId) => {
			ack(status, resp, msgId);
		});
	}

	/**
	 * 监听到机器人一句话术播报完成
	 * @param {function} ack {status,resp,msgId}
	 */
	onAiStartDone(ack) {
		this.on('speaking-done', ack);
	}

	/**
	 * 发生网络状态变化
	 * @param {object} data {status:'weak'|'normal'}
	 * @param {string} toWhom
	 * @param {function} ack
	 */
	sendNetworkStatus(data, toWhom, ack) {
		this.sendMessage('network-status', data, toWhom, (status, resp, msgId) => {
			ack(status, resp, msgId);
		});
	}

	/**
	 *
	 * 监听对方网络状态变化
	 * @param {function} ack
	 */
	onNetworkStatus(ack) {
		this.on('network-status', ack);
	}

	shareResource(type, url, name, toWhom, ack) {
		let data = {
			type: type,
			url: url,
			name: name,
		};
		this.sendMessage('link', data, toWhom, (status, resp, msgId) => {
			ack(status, resp, msgId);
		});
	}

	/**
	 * 根据文件详情发送文件资源<主要用于IM里面发送文件图片等>
	 * @param {string} type 类型
	 * @param {object} fileData 文件对象 { name: 文件名称(String), size: 文件大小(Number), url: 文件地址(String) }
	 * @param {string} toWhom  发送给谁 clientId具体的用户 'customers'= 访客;'agents'= 坐席;'others'= 除了自己的所有人; 'all' = 所有人;
	 * @param {function} ack 发送结果回调
	 */
	shareResourceWithFileInfo(type, fileData, toWhom, ack) {
		let data = { ...fileData, type };
		this.sendMessage('link', data, toWhom, (status, resp, msgId) => {
			ack(status, resp, msgId);
		});
	}

	onSharedResource(ack) {
		this.on('link', ack);
	}

	/**
	 * 发送文本消息
	 * @param {string} message 文本内容
	 * @param {string} toWhom clientId具体的用户 'customers'= 访客;'agents'= 坐席;'others'= 除了自己的所有人; 'all' = 所有人;
	 * @param {function} ack 发送结果回调 {status,resp,msgId}
	 */
	sendTextMessage(message, toWhom, ack) {
		this.sendMessage('text', message, toWhom, (status, resp, msgId) => {
			ack(status, resp, msgId);
		});
	}

	/**
	 * 收到文本消息
	 * @param {function} ack {data:string()}
	 */
	onTextMessage(ack) {
		this.on('text', ack);
	}

	/**
	 * 发送ASR文本消息
	 * @param {string} message 文本内容
	 * @param {string} toWhom clientId具体的用户 'customers'= 访客;'agents'= 坐席;'others'= 除了自己的所有人; 'all' = 所有人;
	 * @param {function} ack 发送结果回调 {status,resp,msgId}
	 */
	sendAsrTextMessage(message, toWhom, ack) {
		this.sendMessage('text-asr', message, toWhom, (status, resp, msgId) => {
			ack(status, resp, msgId);
		});
	}

	/**
	 * 收到Asr文本消息
	 * @param {function} ack {data:string()}
	 */
	onAsrTextMessage(ack) {
		this.on('text-asr', ack);
	}

	/**
	 * 发送TTS辅助播报文本消息,用户坐席端tts辅助播报
	 * @param {string} message 文本内容
	 * @param {string} toWhom clientId具体的用户 'customers'= 访客;'agents'= 坐席;'others'= 除了自己的所有人; 'all' = 所有人;
	 * @param {function} ack 发送结果回调 {status,resp,msgId}
	 */
	sendTTSBroadMessage(message, toWhom, ack) {
		this.sendMessage('text-tts-agent', message, toWhom, (status, resp, msgId) => {
			ack(status, resp, msgId);
		});
	}

	/**
	 * 开始tts辅助播报
	 * @param {string} message 文本内容
	 * @param {string} toWhom clientId具体的用户 'customers'= 访客;'agents'= 坐席;'others'= 除了自己的所有人; 'all' = 所有人;
	 * @param {function} ack 发送结果回调 {status,resp,msgId}
	 */
	startTtsSpeaking(message, toWhom, ack) {
		this.sendMessage('start-tts-speaking', message, toWhom, (status, resp, msgId) => {
			ack(status, resp, msgId);
		});
	}

	/**
	 * 取消tts辅助播报
	 * @param {string} message 文本内容
	 * @param {string} toWhom clientId具体的用户 'customers'= 访客;'agents'= 坐席;'others'= 除了自己的所有人; 'all' = 所有人;
	 * @param {function} ack 发送结果回调 {status,resp,msgId}
	 */
	cancelTtsSpeaking(message, toWhom, ack) {
		this.sendMessage('cancel-tts-speaking', message, toWhom, (status, resp, msgId) => {
			ack(status, resp, msgId);
		});
	}

	/**
	 * 收到Asr文本消息
	 * @param {function} ack {data:string()}
	 */
	onTTSTextMessage(ack) {
		this.on('text-tts-agent', ack);
	}

	/**
	 * 发送tts文本消息
	 * @param {string} message 文本内容
	 * @param {string} toWhom clientId具体的用户 'customers'= 访客;'agents'= 坐席;'others'= 除了自己的所有人; 'all' = 所有人;
	 * @param {function} ack 发送结果回调 {status,resp,msgId}
	 */
	sendTtsTextMessage(message, toWhom, ack) {
		this.sendMessage('text-tts', message, toWhom, (status, resp, msgId) => {
			ack(status, resp, msgId);
		});
	}

	/**
	 * 收到tts文本消息
	 * @param {function} ack {data:string()}
	 */
	onTtsTextMessage(ack) {
		this.on('text-tts', ack);
	}

	/**
	 * 告知服务端user的streamid
	 * @param {object} data {streamId:'xxxxx'}
	 * @param {string} to clientId具体的用户 'customers'= 访客;'agents'= 坐席;'others'= 除了自己的所有人; 'all' = 所有人;
	 */
	noticeStreamId(data, to = '') {
		this.sendMessage('notice-participant-streamId', data, to, (status, resp, msgId) => {
			console.log(`ivcs-js-sdk:notice-participant-streamId status:${status},response:${resp},msgId:${msgId}`);
		});
	}

	/**
	 * 发送预录制视频的文本消息
	 * @param {string} message 文本内容
	 * @param {string} toWhom clientId具体的用户 'customers'= 访客;'agents'= 坐席;'others'= 除了自己的所有人; 'all' = 所有人;
	 * @param {function} ack 发送结果回调 {status,resp,msgId}
	 */
	sendPreRecordVideoTextMessage(message, toWhom, ack) {
		this.sendMessage('text-pre-recorded', message, toWhom, (status, resp, msgId) => {
			ack(status, resp, msgId);
		});
	}

	/**
	 * 收到预录制视频文本消息
	 * @param {function} ack {data:string()}
	 */
	onPreRecordVideoTextMessage(ack) {
		this.on('text-pre-recorded', ack);
	}

	/**
	 * 移除监听事件
	 */
	removeListener(event) {
		this.removeAllListeners(event);
	}

	/**
	 * 发送AI能力识别的结果信息
	 * @param {*} message
	 * @param {*} toWhom
	 * @param {*} ack
	 */
	sendAICheckoutMessage(message, toWhom, ack) {
		this.sendMessage('ai-checkout', message, toWhom, (status, resp, msgId) => {
			ack(status, resp, msgId);
		});
	}

	/**
	 * 收到AI能力识别结果信息
	 * @param {*} ack
	 */
	onAICheckoutMessage(ack) {
		this.on('ai-checkout', ack);
	}

	/**
	 * 发送AI模版节点完成情况<目前用于AI机器人完成相应节点后推送该节点的处理过程和结果给到访客>
	 * @param {object} WorkflowProgressData {{  modId: string(ModID),         //节点ID
												modName: string(ModName),     //节点名称
												modType: enum(['confirmation',]), ,     //模块类型，如“信息确认”、“信息收集”
												result: enum(ResultSet),         //节点结果
												data: object(ProgressData),   //具体的数据
												nextModId: string(NextModID), //下一步节点ID
												startedAt: string(StartedAt), //开始时间
												endedAt: string(EndedAt),     //结束时间
												duration: string(DurationMS), //持续时间
											}}
	 * @param {string} from  发送消息的来源，默认就是当前用户
	 * @param {string} toWhom 发送给谁 clientId具体的用户 'customers'= 访客;'agents'= 坐席;'others'= 除了自己的所有人; 'all' = 所有人;
	 * @param {function} ack 发送结果回调
	 */
	sendWorkflowProgress(WorkflowProgressData, from, toWhom, ack) {
		this.sendMessageFromTo('workflow-progress', WorkflowProgressData, from, toWhom, (status, resp, msgId) => {
			ack(status, resp, msgId);
		});
	}

	/**
	 * 监听AI机器人走完当前节点后，返回访客处理该节点的过程和结果数据
	 * @param {function} ack callback WorkflowProgressData
	 */
	onWorkflowProgress(ack) {
		this.on('workflow-progress', ack);
	}

	sendMessageConfirmation(message, toWhom, ack) {
		this.sendMessage('message-confirmation', message, toWhom, (status, resp, msgId) => {
			ack(status, resp, msgId);
		});
	}

	onMessageConfirmation(ack) {
		this.on('message-confirmation', ack);
	}

	/**
	* @deprecated not support with ccs
	sendForm(formData, toWhom, ack) {
		this.sendMessage('form', formData, toWhom, (status, resp, msgId) => {
			ack(status, resp, msgId);
		});
	}
	onFormData(ack) {
		this.on('form', ack);
	}
	*/

	/**
	 * 发送自定义消息
	 * @param {string|object} data
	 * @param {string} toWhom 发送给谁 clientId具体的用户 'customers'= 访客;'agents'= 坐席;'others'= 除了自己的所有人; 'all' = 所有人;
	 * @param {function} ack
	 */
	sendCustomMessage(data, toWhom, ack) {
		this.sendMessage('custom', data, toWhom, (status, resp, msgId) => {
			ack(status, resp, msgId);
		});
	}

	onCustomMessage(ack) {
		this.on('custom', ack);
	}

	/**************** start 坐席操作事件 ***************/

	/**
	 * 录屏
	 * @param {string|object} data 默认undefined
	 * @param {string} toWhom 发送给谁 clientId具体的用户 'customers'= 访客;'agents'= 坐席;'others'= 除了自己的所有人; 'all' = 所有人;
	 * @param {function} ack
	 */
	screenRecord(data, toWhom, ack) {
		this.sendMessage('screen-record', data, toWhom, (status, resp, msgId) => {
			ack(status, resp, msgId);
		});
	}

	/**
	 * 停止录屏
	 * @param {string|object} data 默认undefined
	 * @param {string} toWhom 发送给谁 clientId具体的用户 'customers'= 访客;'agents'= 坐席;'others'= 除了自己的所有人; 'all' = 所有人;
	 * @param {function} ack
	 */
	stopScreenRecord(data, toWhom, ack) {
		this.sendMessage('stop-screen-record', data, toWhom, (status, resp, msgId) => {
			ack(status, resp, msgId);
		});
	}

	/**
	 * 屏幕共享
	 * @param {string|object} data 默认undefined
	 * @param {string} toWhom 发送给谁 clientId具体的用户 'customers'= 访客;'agents'= 坐席;'others'= 除了自己的所有人; 'all' = 所有人;
	 * @param {function} ack
	 */
	screenShare(data, toWhom, ack) {
		this.sendMessage('screen-share', data, toWhom, (status, resp, msgId) => {
			ack(status, resp, msgId);
		});
	}

	/**
	 * 停止屏幕共享
	 * @param {string|object} data 默认undefined
	 * @param {string} toWhom 发送给谁 clientId具体的用户 'customers'= 访客;'agents'= 坐席;'others'= 除了自己的所有人; 'all' = 所有人;
	 * @param {function} ack
	 */
	stopScreenShare(data, toWhom, ack) {
		this.sendMessage('stop-screen-share', data, toWhom, (status, resp, msgId) => {
			ack(status, resp, msgId);
		});
	}

	/**
	 * 共享屏幕标注
	 * @param {string|object} data 默认undefined
	 * @param {string} toWhom 发送给谁 clientId具体的用户 'customers'= 访客;'agents'= 坐席;'others'= 除了自己的所有人; 'all' = 所有人;
	 * @param {function} ack
	 */
	screenShareMarkDrawing(data, toWhom, ack) {
		this.sendMessage('screen-share-mark-drawing', data, toWhom, (status, resp, msgId) => {
			ack(status, resp, msgId);
		});
	}

	/**
	 * 停止共享屏幕标注
	 * @param {string|object} data 默认undefined
	 * @param {string} toWhom 发送给谁 clientId具体的用户 'customers'= 访客;'agents'= 坐席;'others'= 除了自己的所有人; 'all' = 所有人;
	 * @param {function} ack
	 */
	stopScreenShareMarkDrawing(data, toWhom, ack) {
		this.sendMessage('stop-screen-share-mark-drawing', data, toWhom, (status, resp, msgId) => {
			ack(status, resp, msgId);
		});
	}

	/**************** end 坐席操作事件 ***************/

	/***********  start 业务指令 *****************/

	/**
	 * 是指坐席端在服务过程中向访客端发送的业务操作要求，譬如推送合同浏览、要求手写签名、截取摄像头画面照片、扫码等。坐席端发出业务指令后，一般需要等待访客端做出业务响应后进行下一步操作
	 * @param {string} cmdName 指令名称 enum('view-resource'|'screenshot'...)
	 * @param {boolean} askForPermission 是否需要对方授权，默认为false
	 * @param {object} cmdData 指令相关数据
	 * @param {string} to 发送给谁 string(clientId) = 会话中指定的用户 'customers' = 访客;'agents' = 坐席;'others' = 除了自己的所有人; 'all' = 所有人
	 * @param {function} ack  {status:enum("ok"|"error"),message:string('')}
	 */
	sendPeerCmd(cmdName, askForPermission = false, cmdData, to, ack) {
		let msg = {
			cmd: cmdName,
			askForPermission: askForPermission,
			data: cmdData,
		};
		this.sendMessage('peer-cmd', msg, to, (status, resp, msgId) => {
			ack(status, resp, msgId);
		});
	}

	/**
	 * 监听/接收对方发送的业务指令
	 * @param {string} cmdName 指令名称 enum('view-resource'|'screenshot'...)
	 * @param {function} ack 返回接收的详情
	 */
	onPeerCmd(cmdName, ack) {
		this.on(`peer-cmd:${cmdName}`, ack);
	}

	/**
	 * 是指坐席端在等待访客端响应期间，因故取消业务指令的消息，以取消前序发出的业务指令。访客端收到取消指令后应当停止该指令的业务操作
	 * @param {string} cmdName 指令名称 enum('view-resource'|'screenshot'...)
	 * @param {string} MsgIdOfCmd 前序发送的业务指令id
	 * @param {string} to 发送给谁 string(clientId) = 会话中指定的用户 'customers' = 访客;'agents' = 坐席;'others' = 除了自己的所有人; 'all' = 所有人
	 * @param {string} ack {status:enum("ok"|"error"),message:string('')}
	 * @param {string} reason enum("normal","timeout")
	 * @param {object} cmdData 指令相关数据
	 */
	sendPeerCancel(cmdName, MsgIdOfCmd, to, ack, reason = 'normal', cmdData) {
		let msg = {
			cmd: cmdName,
			id: MsgIdOfCmd,
			reason: reason,
			data: cmdData,
		};
		this.sendMessage('peer-cancel', msg, to, (status, resp, msgId) => {
			ack(status, resp, msgId);
		});
	}

	/**
	 * 监听/接收对方发送的取消业务指令
	 * @param {string} cmdName 指令名称 enum('view-resource'|'screenshot'...)
	 * @param {function} ack 返回接收的详情
	 */
	onPeerCancel(cmdName, ack) {
		this.on(`peer-cancel:${cmdName}`, ack);
	}

	/**
	 * 指访客端收到坐席端的业务指令后，向坐席端发送的务业务操作的状态、结果消息
	 * @param {string} cmdName 指令名称 enum('view-resource'|'screenshot'...)
	 * @param {string} MsgIdOfCmd 对方发送的业务指令id
	 * @param {string} status enum(['accepted','rejected','in-progress','failed','done'])
	 * @param {any} responseData
	 * @param {string} to 发送给谁 string(clientId) = 会话中指定的用户 'customers' = 访客;'agents' = 坐席;'others' = 除了自己的所有人; 'all' = 所有人
	 * @param {string} ack {status:enum("ok"|"error"),message:string('')}
	 */
	sendPeerResponse(cmdName, MsgIdOfCmd, status, responseData, to, ack) {
		let msg = {
			cmd: cmdName,
			id: MsgIdOfCmd,
			status: status,
			data: responseData,
		};
		this.sendMessage('peer-response', msg, to, (status, resp, msgId) => {
			ack(status, resp, msgId);
		});
	}

	/**
	 * 收到用户响应信息
	 * @param {string} cmdName 指令名称 enum('view-resource'|'screenshot'...)
	 * @param {function} ack 返回接收的详情
	 */
	onPeerResponse(cmdName, ack) {
		this.on(`peer-response:${cmdName}`, ack);
	}

	/***********  end 业务指令 *****************/
}

export { CCSClient };

export function refreshCcsTicket(ccsClient, refreshTicketTime) {
	ccsClient.interval = setInterval(function () {
		ccsClient.refreshTicket((refreshStatus, refreshResp) => {
			if (refreshStatus === 'ok') {
				Logger.info('ivcs-js-sdk:ccs sio refreshTicket success', refreshResp, new Date().toISOString());
				ccsClient.ticket = refreshResp.ticket;
			} else {
				Logger.error(`ivcs-js-sdk:ccs sio refreshTicket failed,${new Date().toISOString()}`);
				clearInterval(ccsClient.interval);
				ccsClient.interval = null;
				dispatchEvent(ccsEvent.refreshTicketFailed, errorCode.refreshTicketFailed, 'ccs sio refreshTicketFailed');
			}
		});
	}, refreshTicketTime);
}

export function resumeCcsConnection(ccsClient, ticket, ack) {
	if (!ccsClient.accessToken) {
		Logger.info('ivcs-js-sdk:resumeCcsConnection return while ccsClient accessToken is null time:', new Date().toISOString());
		return;
	}
	if (ticket) {
		ccsClient.resumeConnection(ticket, (resumeConnectionStatus, resumeConnectionResp) => {
			if (resumeConnectionStatus === 'ok') {
				Logger.info(`ivcs-js-sdk:resume ccs connection success,${resumeConnectionResp},${new Date().toISOString()}`);
				ccsClient.ticket = resumeConnectionResp.ticket;
			} else {
				Logger.warning(`ivcs-js-sdk:resume ccs connection failed,${resumeConnectionResp},${new Date().toISOString()}`);
				dispatchEvent(ccsEvent.resumeConnectionFailed, errorCode.resumeConnectionFailed, resumeConnectionResp);
				ccsClient.connected = false; // resume失败，则ccs socketio 无法连接。
			}
			ack(resumeConnectionStatus, resumeConnectionResp);
		});
	} else {
		//not join session
		Logger.info('ivcs-js-sdk:resumeCcsConnection ticket is Empty', new Date().toISOString());
		// dispatchEvent(ccsEvent.resumeConnectionFailed, errorCode.ticketIsEmpty, 'ticket is Empty');
		// ack('error', 'ticket is Empty');
	}
}

export function releaseCcsClient(ccsClient) {
	if (ccsClient) {
		clearInterval(ccsClient.interval);
		ccsClient.interval = null;
		if (!ccsClient.clientId) {
			//未request-session/join-session成功,则等待一段时间后去挂断socketio链接,确保ccs服务端能正确检测到访客挂断
			const waitRequestSessionTime = 10000;
			setTimeout(() => {
				ccsClient.disconnect();
			}, waitRequestSessionTime);
		} else {
			ccsClient.disconnect();
		}
		ccsClient.accessToken = null;
		ccsClient.connected = false;
		ccsClient.ticket = null;
		ccsClient.members = [];
		Logger.info(`ivcs-js-sdk:release ccs client,${new Date().toISOString()}}`);
	}
}

export function validateCcsClient(ccsClient) {
	if (!ccsClient) {
		Logger.warning('ivcs-js-sdk:ccs client is undefined');
		return new IvcsError(ivcsErrorCodeEnum.ERROR_CCS_UNDEFINED, 'ccs client is undefined');
	}
	if (!ccsClient.accessToken) {
		Logger.warning('ivcs-js-sdk:ccs accessToken is undefined or invalid');
		return new IvcsError(ivcsErrorCodeEnum.ERROR_CCS_TOKEN_UNDEFINED, 'ccs accessToken is undefined or invalid');
	}
	if (!ccsClient.connected) {
		Logger.warning('ivcs-js-sdk:ccs socket io is disconnected');
		return new IvcsError(ivcsErrorCodeEnum.ERROR_CCS_DISCONNECT, 'ccs socket io is disconnected');
	}
	return 'ok';
}

/**
 * 建立ccs的socketio链接
 * @param {ccsClient} ccsClient ccsClient对象
 * @param {string} ccsUrl ccs server url
 * @param {string} accessToken
 * @param {function} connectErrorAck connect error callback
 * @param {number} reconnectErrorTime 允许连接error的最大次数
 */
export async function connect(ccsClient, ccsUrl, accessToken, connectErrorAck, reconnectErrorTime = reconnectionAttempts) {
	return await new Promise((resolve, reject) => {
		if (!ccsUrl) {
			let response = new IvcsError(ivcsErrorCodeEnum.ERROR_CCS_CONNECT_FAILED, `connect to ccs error, ccsUrl is empty`);
			reject(response);
			connectErrorAck(response);
			return;
		}
		ccsClient.connect(
			ccsUrl,
			accessToken,
			resp => {
				resolve(resp);
			},
			error => {
				let response = new IvcsError(ivcsErrorCodeEnum.ERROR_CCS_CONNECT_FAILED, `connect to ${ccsUrl} error,${JSON.stringify(error)}`);
				reject(response);
				connectErrorAck(error);
			}
		);
		ccsClient.onConnect(resp => {
			ccsClient.connected = true;
			if (!ccsClient.isReconnected) {
				ccsClient.accessToken = accessToken;
				ccsClient.isReconnected = true;
			} else {
				Logger.info('ivcs-js-sdk:ccsClient resumeCcsConnection ccsUrl', ccsUrl, resp);
				resumeCcsConnection(ccsClient, ccsClient.ticket, (status, message) => {
					if (status !== 'ok') {
						Logger.info(`ivcs-js-sdk:resumeCcsConnection to ${ccsUrl} error,${message}`);
						let response = new IvcsError(ivcsErrorCodeEnum.ERROR_CCS_RESUME_FAILED, `resumeCcsConnection to ${ccsUrl} error,${message}`);
						connectErrorAck(response);
					}
				});
			}
		});
		ccsClient.onDisconnect(reason => {
			Logger.warning('ivcs-js-sdk:ccsClient,ccs socket is disconnected,', reason, new Date().toISOString());
		});
		ccsClient.onDrop(reason => {
			Logger.warning('ivcs-js-sdk:ccsClient,ccs socket io is drop,', reason, new Date().toISOString());
			dispatchEvent(ccsEvent.socketIoDrop, errorCode.socketIoDrop, 'ccs socket io is drop');
			ccsClient.accessToken = null;
			ccsClient.connected = false; // 被系统强制结束后，会触发disconnet io server disconnect,这个时候不需要重连。
			clearInterval(ccsClient.interval); // 移除刷新ccs tickit的定时器
			ccsClient.interval = null;
		});
	});
}

export async function joinSession(ccsClient, joinData) {
	Logger.info('ivcs-js-sdk:joinSession parameter:', joinData);
	return await new Promise((resolve, reject) => {
		ccsClient.joinSession(joinData, (status, joinResult) => {
			if (status === 'ok') {
				Logger.info(`ivcs-js-sdk:join session ${joinData.sessionId} success,${JSON.stringify(joinResult)},${new Date().toISOString()}`);
				ccsClient.ticket = joinResult.ticket;
				resolve(joinResult);
			} else {
				Logger.info(`ivcs-js-sdk:join session ${joinData.sessionId} error,${joinResult},${new Date().toISOString()}`);
				let response;
				if (joinResult === 'Non-exist session') {
					response = new IvcsError(ivcsErrorCodeEnum.ERROR_CCS_NOT_EXIST_SESSION, `join session ${joinData.sessionId} error,${joinResult}`);
				} else if (joinResult === 'existTransfer') {
					response = new IvcsError(ivcsErrorCodeEnum.SESSION_IN_TRANSFER, `join session ${joinData.sessionId} error,${joinResult}`);
				} else {
					response = new IvcsError(ivcsErrorCodeEnum.ERROR_CCS_SESSION_MESSAGE_FAILED, `join session ${joinData.sessionId} error,${joinResult}`);
				}
				reject(response);
			}
		});
	});
}

export async function readyToTalk(participantId, user, role, ccsClient) {
	return await new Promise((resolve, reject) => {
		ccsClient.readyToTalk(participantId, user, role, (status, resp) => {
			if (status === 'ok') {
				resolve();
			} else {
				Logger.info(`ivcs-js-sdk:tell readyToTalk WhileStreamPublishSuccess error,${resp},${new Date().toISOString()}`);
				let response = new IvcsError(ivcsErrorCodeEnum.ERROR_CCS_SESSION_MESSAGE_FAILED, `tell readyToTalk WhileStreamPublishSuccess error,${resp}`);
				reject(response);
			}
		});
	});
}

export async function inviteOtherAgent(ccsClient, inviteData) {
	return await new Promise((resolve, reject) => {
		ccsClient.invite(inviteData, (status, resp) => {
			if (status === 'ok') {
				if (resp && resp.hasAvailableAgent) {
					resolve(resp);
				} else {
					const status = resp.invitationStatus;
					const result = new IvcsError('400', status);
					switch (status) {
						case 'timeout':
							result.code = '401';
							break;
						case 'notfound':
							result.code = '404';
							break;
						case 'offday':
							result.code = '406';
							break;
						case 'offtime':
							result.code = '407';
							break;
						case 'scenariodisabled':
							result.code = '408';
							break;
						case 'outofquota':
							result.code = '409';
							break;
						case 'outofquota':
							result.code = '401';
							break;
						case 'agentrejected':
							result.code = '410';
							break;
						case 'otherCustomerAccept':
							result.code = '411';
							break;
						case 'multiCustomerForbidAI':
							result.code = '412';
							break;
						case 'rejected':
							result.code = '413';
							break;
						case 'ringingTimeout':
							result.code = '414';
							break;
						case 'canceled':
							result.code = '415';
							break;
						case 'customerRolesEmpty':
							result.code = '416';
							break;
						case 'scenarioRolesEmpty':
							result.code = '417';
							break;
						case 'scenarioRolesNotMatched':
							result.code = '418';
							break;
						case 'scenarioRoleHasExistedInSession':
							result.code = '419';
							break;
						case 'groupsDoNotMatch':
							result.code = '420';
							break;
						case 'labelsDoNotMatch':
							result.code = '421';
							break;
						case 'agentNotBoundGroupsInScenario':
							result.code = '422';
							break;
						case 'agentNotCheckInGroupsInScenario':
							result.code = '423';
							break;
						default:
							break;
					}
					reject(result);
				}
			} else {
				// http://192.168.31.91:8090/pages/viewpage.action?pageId=6063298
				Logger.info(`ivcs-js-sdk:invite other agent error,${JSON.stringify(inviteData)},${resp},${new Date().toISOString()}`);
				let response = new IvcsError(ivcsErrorCodeEnum.ERROR_CCS_SESSION_MESSAGE_FAILED, `invite with parameter ,${JSON.stringify(inviteData)}, error,${resp}`);
				if (status === 'audioVideoRecordingCompleted') {
					response = new IvcsError('601', resp); // 当前转接业务已完成，暂无法转接！
				} else if (status === 'audioVideoRecordingWaited') {
					response = new IvcsError('602', resp); // 当前转接业务已完成，暂无法转接！
				} else if (status === 'audioVideoRecordingInProgress') {
					response = new IvcsError('603', resp); // 前转接业务正在进行中，暂无法转接！
				} else if (status === 'audioVideoRerecordingInProgress') {
					response = new IvcsError('604', resp); // 当前转接业务正在进行中，暂无法转接！
				} else if (status === 'transferCallExists') {
					response = new IvcsError('605', resp); // 坐席转接/三方（invite）的时候触发，存在转接中的的请求；
				} else if (status === 'multipleCallExists') {
					response = new IvcsError('606', resp); // 坐席转接/三方（invite）的时候触发，存在三方的的请求
				} else if (status === 'notAllowTransferInMultiple') {
					response = new IvcsError('607', resp); // 多坐席情况下不支持场景转接
				} else if (status === 'disconnected') {
					response = new IvcsError(ivcsErrorCodeEnum.ERROR_CCS_SENDMESSAGE_DISCONNECTED, resp);
				} else if (status === 'timeout') {
					response = new IvcsError(ivcsErrorCodeEnum.ERROR_CCS_SENDMESSAGE_TIMEOUT, resp);
				} else if (status === 'ack-timeout') {
					response = new IvcsError(ivcsErrorCodeEnum.ERROR_CCS_SENDMESSAGE_ACKTIMEOUT, resp);
				} else {
					response = new IvcsError(ivcsErrorCodeEnum.ERROR_CCS_SESSION_MESSAGE_FAILED, resp);
				}
				reject(response);
			}
		});
	});
}

/**
 * 通知访客正在转接中
 * @param {CCSClient} ccsClient
 * @param {object} transferInfo 转接的相关信息,业务场景id/坐席id/业务场景名称等
 */
export async function notifyTransference(ccsClient, transferInfo) {
	return await new Promise((resolve, reject) => {
		ccsClient.holdOn(
			'customers',
			(status, resp, msgId) => {
				if (status === 'ok') {
					resolve();
				} else {
					Logger.info(`ivcs-js-sdk:tell Customer hold on error,${resp},${new Date().toISOString()}`);
					let response = new IvcsError(ivcsErrorCodeEnum.ERROR_CCS_SESSION_MESSAGE_FAILED, `tell Customer hold on,msgId ${msgId},error,${resp}`);
					reject(response);
				}
			},
			transferInfo
		);
	});
}

export async function quitSession(data, ccsClient) {
	return await new Promise((resolve, reject) => {
		if (!ccsClient) {
			reject(new IvcsError(ivcsErrorCodeEnum.ERROR_CCS_UNDEFINED, 'ccs client is null'));
			return;
		}
		ccsClient.quitSession(data, (status, resp) => {
			Logger.info('ivcs-js-sdk:quit session result,', status, resp, data, new Date().toISOString());
			if (status === 'ok') {
				resolve(resp);
			} else {
				reject(resp);
			}
			releaseCcsClient(ccsClient);
		});
		ccsClient.accessToken = null; // 退出会话后，accessToken失效
	});
}

export function getCustomerClientInfoWithMembers(members) {
	let customerClientType = 'web';
	// eslint-disable-next-line no-unused-vars
	members.map((currentValue, index, arr) => {
		if (currentValue.role === 'customer') {
			let clientInfo = currentValue.clientInfo ? currentValue.clientInfo : undefined;
			customerClientType = clientInfo ? clientInfo.type : undefined;
			Logger.info('ivcs-js-sdk:customerClientInfo,', clientInfo, customerClientType);
		}
	});
	return customerClientType;
}

/**
 * 监听ccs异常事件
 * @param {function} callback
 * @returns Array,事件列表，可供移除
 */
export function ccsErrorEvent(callback) {
	let resumeConnectEvent = addEventListener(ccsEvent.resumeConnectionFailed, info => {
		removeEventListener(ccsEvent.resumeConnectionFailed, resumeConnectEvent);
		callback(info);
	});
	let disconnectEvent = addEventListener(ccsEvent.socketIoDisconnected, info => {
		removeEventListener(ccsEvent.socketIoDisconnected, disconnectEvent);
		callback(info);
	});
	let dropEvent = addEventListener(ccsEvent.socketIoDrop, info => {
		removeEventListener(ccsEvent.socketIoDrop, dropEvent);
		callback(info);
	});
	let resconnectFailedEvent = addEventListener(ccsEvent.socketIoReconnectFailed, info => {
		removeEventListener(ccsEvent.socketIoReconnectFailed, resconnectFailedEvent);
		callback(info);
	});

	const eventList = [
		{ eventName: ccsEvent.resumeConnectionFailed, eventListenerFunction: resumeConnectEvent },
		{ eventName: ccsEvent.socketIoDisconnected, eventListenerFunction: disconnectEvent },
		{ eventName: ccsEvent.socketIoDrop, eventListenerFunction: dropEvent },
		{ eventName: ccsEvent.socketIoReconnectFailed, eventListenerFunction: resconnectFailedEvent },
	];
	return eventList;
}

/**
 * 移除ccs错误监听事件
 * @param {Array} eventList
 */
export function removeCcsErrorEvent(eventList) {
	if (eventList && eventList.length) {
		eventList.filter(event => {
			console.log('removeCcsErrorEvent ', event, event.eventName, event.eventListenerFunction);
			removeEventListener(event.eventName, event.eventListenerFunction);
		});
	}
}

export function sendTextMessage(ccsClient, message, clientId, onSuccess, onFailure) {
	let isValid = validateCcsClient(ccsClient);
	if (isValid != 'ok') {
		onFailure(isValid);
		return;
	}
	ccsClient.sendTextMessage(message, clientId, (status, resp, msgId) => {
		if (status === 'ok') {
			onSuccess();
		} else {
			Logger.info(`ivcs-js-sdk:send text message:${message} to ${clientId} failed,reason:${resp},msgId ${msgId},${new Date().toISOString()}`);
			onFailure(resp);
		}
	});
}

export function onTextMessage(ccsClient, ack) {
	let isValid = validateCcsClient(ccsClient);
	if (isValid != 'ok') {
		Logger.warning(`ivcs-js-sdk:onTextMessage error,${JSON.stringify(isValid)}`);
		return;
	}
	ccsClient.onTextMessage(data => {
		ack(data);
	});
}

export function sendAICheckoutMessage(ccsClient, message, clientId, onSuccess, onFailure) {
	// eslint-disable-next-line no-undef
	let isValid = validateCcsClient(client.ccsClient);
	if (isValid != 'ok') {
		onFailure(isValid);
	}
	ccsClient.sendAICheckoutMessage(message, clientId, (status, resp, msgId) => {
		if (status === 'ok') {
			onSuccess();
		} else {
			Logger.info(`ivcs-js-sdk:send ai-checkout message:${message} to ${clientId} failed,reason:${resp},msgId ${msgId},${new Date().toISOString()}`);
			onFailure(resp);
		}
	});
}

export function onAICheckoutMessage(ccsClient, ack) {
	// eslint-disable-next-line no-undef
	let isValid = validateCcsClient(client.ccsClient);
	if (isValid != 'ok') {
		ack(isValid);
	}
	ccsClient.onAICheckoutMessage(data => {
		ack(data);
	});
}

export function sendCustomMessage(ccsClient, data, toWhom, onSuccess, onFailure) {
	let isValid = validateCcsClient(ccsClient);
	if (isValid != 'ok') {
		onFailure(isValid);
		return;
	}
	ccsClient.sendCustomMessage(data, toWhom, (status, resp, msgId) => {
		if (status === 'ok') {
			onSuccess(msgId);
		} else {
			Logger.info(`ivcs-js-sdk:send custom message: ${data} to ${toWhom} failed,reason:${resp},msgId ${msgId},${new Date().toISOString()}`);
			onFailure(resp);
		}
	});
}

export function requestScreenShareWithOthers(ccsClient, toWhom, onSuccess, onFailure) {
	let isValid = validateCcsClient(ccsClient);
	if (isValid != 'ok') {
		onFailure(isValid);
		return;
	}
	ccsClient.requestScreenShare(false, toWhom, (status, resp, msgId) => {
		if (status === 'ok') {
			onSuccess(msgId);
		} else {
			Logger.info(`ivcs-js-sdk:send request screen share message to ${toWhom} failed,reason:${resp},msgId ${msgId},${new Date().toISOString()}`);
			onFailure(resp);
		}
	});
}

export function receiveRequestScreenShareMessage(ccsClient, ack) {
	let isValid = validateCcsClient(ccsClient);
	if (isValid != 'ok') {
		Logger.warning(`ivcs-js-sdk:receiveRequestScreenShareMessage error,${JSON.stringify(isValid)}`);
		return;
	}
	ccsClient.onRequestScreenShare(data => {
		ack(data);
	});
}

export function releaseScreenShare(ccsClient, toWhom, onSuccess, onFailure) {
	let isValid = validateCcsClient(ccsClient);
	if (isValid != 'ok') {
		onFailure(isValid);
		return;
	}
	ccsClient.releaseScreen(false, toWhom, (status, resp, msgId) => {
		if (status === 'ok') {
			onSuccess(msgId);
		} else {
			Logger.info(`ivcs-js-sdk:send request screen share message to ${toWhom} failed,reason:${resp},msgId ${msgId},${new Date().toISOString()}`);
			onFailure(resp);
		}
	});
}

export function receiveReleaseScreenShareMessage(ccsClient, ack) {
	let isValid = validateCcsClient(ccsClient);
	if (isValid != 'ok') {
		Logger.warning(`ivcs-js-sdk:receiveReleaseScreenShareMessage error,${JSON.stringify(isValid)}`);
		return;
	}
	ccsClient.onReleaseScreen(data => {
		ack(data);
	});
}

export function sendDeviceScreenSize(ccsClient, data, toWhom, onSuccess, onFailure) {
	let isValid = validateCcsClient(ccsClient);
	if (isValid != 'ok') {
		onFailure(isValid);
		return;
	}
	ccsClient.sendDeviceScreenSize(data, toWhom, (status, resp, msgId) => {
		if (status === 'ok') {
			onSuccess(msgId);
		} else {
			Logger.info(`ivcs-js-sdk:send device screenSize message to ${toWhom} failed,reason:${resp},msgId ${msgId},${new Date().toISOString()}`);
			onFailure(resp);
		}
	});
}

export function onDeviceScreenSize(ccsClient, ack) {
	let isValid = validateCcsClient(ccsClient);
	if (isValid != 'ok') {
		Logger.warning(`ivcs-js-sdk:onDeviceScreenSize error,${JSON.stringify(isValid)}`);
		return;
	}
	ccsClient.onDeviceScreenSize(data => {
		ack(data);
	});
}

export function markScreen(ccsClient, data, toWhom, onSuccess, onFailure) {
	let isValid = validateCcsClient(ccsClient);
	if (isValid != 'ok') {
		onFailure(isValid);
		return;
	}
	ccsClient.markScreen(data, toWhom, (status, resp, msgId) => {
		if (status === 'ok') {
			onSuccess(msgId);
		} else {
			Logger.info(`ivcs-js-sdk:markScreen message ${data}to ${toWhom} failed,reason:${resp},msgId ${msgId},${new Date().toISOString()}`);
			onFailure(resp);
		}
	});
}

export function onScreenPoint(ccsClient, ack) {
	let isValid = validateCcsClient(ccsClient);
	if (isValid != 'ok') {
		Logger.warning(`ivcs-js-sdk:onScreenPoint error,${JSON.stringify(isValid)}`);
		return;
	}
	ccsClient.onScreenPoint(data => {
		ack(data);
	});
}

export function onCustomMessage(ccsClient, ack) {
	let isValid = validateCcsClient(ccsClient);
	if (isValid != 'ok') {
		Logger.warning(`ivcs-js-sdk:onCustomMessage error,${JSON.stringify(isValid)}`);
		return;
	}
	ccsClient.onCustomMessage(data => {
		ack(data);
	});
}

/**
 * 上传资源文件并发送给对方
 * @param {ccsClient} ccsClient ccsClient对象
 * @param {string} uploadUrl 上传文件接口url
 * @param {string} type 文件类型
 * @param {formData} formData 文件formData
 * @param {string} clientId 发送给谁 clientId具体的用户 'customers' = 访客;'agents' = 坐席;'others' = 除了自己的所有人; 'all' = 所有人;
 * @param {function} onSuccess 成功
 * @param {function} onFailure 失败
 * @param {string} token 用户登录后返回的token，用于api请求鉴权
 */
export async function shareResource(ccsClient, uploadUrl, type, formData, clientId, onSuccess, onFailure, token) {
	let supportType = ['doc', 'pic', 'video', 'audio', 'page', 'paper'];
	if (supportType.indexOf(type) === -1) {
		onFailure(`type is invalid,only support 'doc','pic', 'video', 'audio', 'page', 'paper'`);
		return;
	}
	let isValid = validateCcsClient(ccsClient);
	if (isValid != 'ok') {
		onFailure(isValid);
		return;
	}
	try {
		let uploadResult = await uploadFile(uploadUrl, formData, token);
		ccsClient.shareResource(type, uploadResult.url, uploadResult.originalname, clientId, (status, resp, msgId) => {
			if (status === 'ok') {
				onSuccess(uploadResult);
			} else {
				Logger.info(`ivcs-js-sdk:send shareResource message failed,${resp},msgId${msgId},${new Date().toISOString()}`);
				onFailure(resp);
			}
		});
	} catch (error) {
		onFailure(error);
	}
}

export function onResourceMessage(ccsClient, ack) {
	let isValid = validateCcsClient(ccsClient);
	if (isValid != 'ok') {
		Logger.warning(`ivcs-js-sdk:onResourceMessage error,${JSON.stringify(isValid)}`);
		return;
	}
	ccsClient.onSharedResource(data => {
		ack(data);
	});
}

export function sendForm(ccsClient, formData, clientId, onSuccess, onFailure) {
	let isValid = validateCcsClient(ccsClient);
	if (isValid != 'ok') {
		onFailure(isValid);
		return;
	}
	ccsClient.sendForm(formData, clientId, (status, resp, msgId) => {
		if (status === 'ok') {
			onSuccess();
		} else {
			Logger.info(`ivcs-js-sdk:send form template failed,reason:${resp},msgId${msgId},${new Date().toISOString()}`);
			onFailure(resp);
		}
	});
}

export function onForm(ccsClient, ack) {
	let isValid = validateCcsClient(ccsClient);
	if (isValid != 'ok') {
		Logger.warning(`ivcs-js-sdk:onForm error,${JSON.stringify(isValid)}`);
		return;
	}
	ccsClient.onFormData(resp => {
		ack(resp);
	});
}

export function onHoldOn(ccsClient, ack) {
	let isValid = validateCcsClient(ccsClient);
	if (isValid != 'ok') {
		Logger.warning(`ivcs-js-sdk:onHoldOn error,${JSON.stringify(isValid)}`);
		return;
	}
	ccsClient.onHoldOn(resp => {
		ack(resp);
	});
}

export function onJoinSession(ccsClient, ack) {
	let isValid = validateCcsClient(ccsClient);
	if (isValid != 'ok') {
		Logger.warning(`ivcs-js-sdk:onJoinSession error,${JSON.stringify(isValid)}`);
		return;
	}
	ccsClient.onJoinedSession(resp => {
		Logger.info('ivcs-js-sdk:other member join session,', resp, new Date().toISOString());
		ack(resp);
	});
}

export function onQuitSession(ccsClient, ack) {
	let isValid = validateCcsClient(ccsClient);
	if (isValid != 'ok') {
		Logger.warning(`ivcs-js-sdk:onQuitSession error,${JSON.stringify(isValid)}`);
		return;
	}
	ccsClient.onQuitSession(resp => {
		Logger.info('ivcs-js-sdk:other member quit session,', resp, new Date().toISOString());
		ack(resp);
	});
}

/**
 * 监听会话中的参与者的状态，加入或者离开
 * @param {ccsClient} ccsClient ccsClient对象
 * @param {function} customerQuitAck 访客离开的回调,访客离开会话会被结束
 * @param {function} agentJoinAck 坐席加入会话回调
 * @param {function} customerJoinAck 访客加入会话
 */
export function sessionMemberJoinQuitEvent(ccsClient, customerQuitAck, agentJoinAck, customerJoinAck) {
	onJoinSession(ccsClient, resp => {
		// ccsClient.members.push(resp.data);
		if (resp.data && resp.data.role === 'agent') {
			agentJoinAck(resp.data);
			dispatchEvent('onAgentJoinSession', null, resp.data);
		} else if (resp.data && resp.data.role === 'customer') {
			dispatchEvent('onCustomerJoinSession', null, resp.data);
			customerJoinAck(resp.data);
		}
	});
	onQuitSession(ccsClient, resp => {
		if (resp.data.role === 'customer') {
			customerQuitAck(resp.data);
			dispatchEvent('onCustomerQuitSession', null, resp.data);
		} else if (resp.data.role === 'agent') {
			dispatchEvent('onAgentQuitSession', null, resp.data);
		}
		// removeObjectWithId(ccsClient.members, resp.data.id);
	});
}

/**
 * 监听ccs-client 的 invitationProgress
 * @param {ccsClient} ccsClient
 * @param {function} ack
 */
export function onInvitationProgress(ccsClient, ack) {
	return ccsClient.onInvitationProgress(progress => {
		Logger.info(`ivcs-js-sdk:invite progress,${JSON.stringify(progress)},${new Date().toISOString()}`);
		let status = progress.data.status;
		let response = {
			code: '',
			message: '',
			invitationId: progress.data.id,
			status: status,
			type: progress.data.type,
			to: progress.data.to,
			from: progress.data.from,
		};
		if (status === 'offday') {
			response.code = '406';
			response.message = 'request is offday';
		} else if (status === 'offtime') {
			response.code = '407';
			response.message = 'request is offtime';
		} else if (status === 'notfound') {
			response.code = '404';
			response.message = 'not found others';
		} else if (status === 'timeout') {
			response.code = '401';
			response.message = 'invite timeout';
		} else if (status === 'waiting') {
			response.code = '402';
			let waitingCode = progress.data.waitingReasonCode ? progress.data.waitingReasonCode : '-1';
			response.waitingCode = waitingCode;
			let waitingNumber = progress.data && progress.data.waitingNum ? progress.data.waitingNum : 0;
			let position = progress.data && progress.data.position ? progress.data.position : 1;
			response.waitingNumber = waitingNumber;
			response.position = position;
			if (waitingCode === '1000') {
				response.message = 'system is distributing agent';
			} else if (waitingCode === '1001') {
				response.message = 'no available agent was found';
			} else if (waitingCode === '1002') {
				response.message = 'the designated agent of the invitation is not logged in';
			} else if (waitingCode === '1003') {
				// 指定坐席正在忙
				response.message = 'the current state of the specified agent is not available';
			} else if (waitingCode === '1004') {
				response.message = 'disconnect when the specified agent rings';
			}
		} else if (status === 'connecting') {
			response.code = '403';
			response.message = 'agent connecting';
		} else if (status === 'accepted') {
			response.code = '405';
			response.message = 'agent accepted';
		} else if (status === 'scenariodisabled') {
			response.code = '408';
			response.message = 'scenario disabled';
		} else if (status === 'outofquota') {
			response.code = '409';
			response.message = 'outofquota';
		} else if (status === 'agentrejected') {
			response.code = '410';
			response.message = 'agentrejected';
		} else if (status === 'otherCustomerAccept') {
			response.code = '411';
			response.message = 'otherCustomerAccept';
		} else if (status === 'multiCustomerForbidAI') {
			response.code = '412';
			response.message = 'multiCustomerForbidAI';
		} else if (status === 'rejected') {
			response.code = '413';
			response.message = 'the specified agent is rejected';
		} else if (status === 'ringingTimeout') {
			// 振铃超时未接听
			response.code = '414';
			response.message = 'ringingTimeout';
		} else if (status === 'canceled') {
			// 邀请被取消
			response.code = '415';
			response.message = 'canceled';
		} else if (status === 'customerRolesEmpty') {
			// 访客角色为空
			response.code = '416';
			response.message = 'customerRolesEmpty';
		} else if (status === 'scenarioRolesEmpty') {
			// 场景角色为空
			response.code = '417';
			response.message = 'scenarioRolesEmpty';
		} else if (status === 'scenarioRolesNotMatched') {
			// 场景角色不匹配
			response.code = '418';
			response.message = 'scenarioRolesNotMatched';
		} else if (status === 'scenarioRoleHasExistedInSession') {
			// 会话中当前角色已存在
			response.code = '419';
			response.message = 'scenarioRoleHasExistedInSession';
		} else if (status === 'groupsDoNotMatch') {
			// 指定了分组链呼叫时，对应业务场景下不包含分组链中的任何分组
			response.code = '420';
			response.message = 'groupsDoNotMatch';
		} else if (status === 'labelsDoNotMatch') {
			// 指定坐席并且指定了标签呼叫，派工时发现指定的坐席没有绑定该标签
			response.code = '421';
			response.message = 'labelsDoNotMatch';
		} else if (status === 'agentNotBoundGroupsInScenario') {
			// 转接的坐席未绑定当前业务场景对应的分组
			response.code = '422';
			response.message = 'agentNotBoundGroupsInScenario';
		} else if (status === 'agentNotCheckInGroupsInScenario') {
			// 转接的坐席未签入当前业务场景对应的分组
			response.code = '423';
			response.message = 'agentNotCheckInGroupsInScenario';
		}
		ack(response);
	});
}

/**
 * 发送获取地理位置指令
 * @param {object} ccsClient ccsClient对象
 * @param {boolean} askForPermission  是否需要对方授权，默认是false
 * @param {string} toWhom 发送给谁 clientId具体的用户 'customers' = 访客;'agents' = 坐席;'others' = 除了自己的所有人; 'all' = 所有人;
 * @param {function} onSuccess 发送成功
 * @param {function} onFailure 发送失败
 */
export function sendRequestLocation(ccsClient, askForPermission, toWhom, onSuccess, onFailure) {
	let isValid = validateCcsClient(ccsClient);
	if (isValid != 'ok') {
		onFailure(isValid);
		return;
	}
	ccsClient.requestLocation(askForPermission, toWhom, (status, resp, msgId) => {
		if (status === 'ok') {
			onSuccess(msgId);
		} else {
			Logger.info(`ivcs-js-sdk:send request-location message to ${toWhom} failed,reason:${resp},msgId ${msgId},${new Date().toISOString()}`);
			dealWithCcsSendMessageResponse(status, resp, onFailure);
			// let response = new IvcsError('5010', resp);
			// onFailure(response);
		}
	});
}

/**
 * 监听收到获取地理位置指令
 * @param {object} ccsClient ccsClient对象
 * @param {function} ack 回调，表明收到获取地理位置指令
 */
export function onRequestLocation(ccsClient, ack) {
	let isValid = validateCcsClient(ccsClient);
	if (isValid != 'ok') {
		Logger.warning(`ivcs-js-sdk:onRequestLocation error,${JSON.stringify(isValid)}`);
		return;
	}
	ccsClient.onRequestLocation(data => {
		ack(data);
	});
}

/**
 * 发送获取地理位置指令
 * @param {object} ccsClient ccsClient对象
 * @param {boolean} askForPermission  是否需要对方授权，默认是false
 * @param {string} toWhom 发送给谁 clientId具体的用户 'customers' = 访客;'agents' = 坐席;'others' = 除了自己的所有人; 'all' = 所有人;
 * @param {function} onSuccess 发送成功
 * @param {function} onFailure 发送失败
 */
export function sendScanCode(ccsClient, askForPermission, toWhom, onSuccess, onFailure) {
	let isValid = validateCcsClient(ccsClient);
	if (isValid != 'ok') {
		onFailure(isValid);
		return;
	}
	ccsClient.scanCode(askForPermission, toWhom, (status, resp, msgId) => {
		if (status === 'ok') {
			onSuccess(msgId);
		} else {
			Logger.info(`ivcs-js-sdk:send scan-code message to ${toWhom} failed,reason:${resp},msgId ${msgId},${new Date().toISOString()}`);
			dealWithCcsSendMessageResponse(status, resp, onFailure);
			// let response = new IvcsError('5011', resp);
			// onFailure(response);
		}
	});
}

/**
 * 监听收到获取地理位置指令
 * @param {object} ccsClient ccsClient对象
 * @param {function} ack 回调，表明收到获取地理位置指令
 */
export function onScanCode(ccsClient, ack) {
	let isValid = validateCcsClient(ccsClient);
	if (isValid != 'ok') {
		Logger.warning(`ivcs-js-sdk:onScanCode error,${JSON.stringify(isValid)}`);
		return;
	}
	ccsClient.onScanCode(data => {
		ack(data);
	});
}

/**
 * 取消发送的事件指令
 * @param {object} ccsClient ccsClient对象
 * @param {string|object} data  instructionName 指令名 | {instructionName:'',data:''}
 * @param {string} toWhom 发送给谁 clientId具体的用户 'customers' = 访客;'agents' = 坐席;'others' = 除了自己的所有人; 'all' = 所有人;
 * @param {function} onSuccess 成功
 * @param {function} onFailure 失败
 */
export function cancelInstruction(ccsClient, data, toWhom, onSuccess, onFailure) {
	let isValid = validateCcsClient(ccsClient);
	if (isValid != 'ok') {
		onFailure(isValid);
		return;
	}
	if (typeof data != 'string' && !data.instructionName) {
		let error = new IvcsError('1001', 'data parameter is error');
		onFailure(error);
		return;
	}
	ccsClient.cancelInstruction(data, toWhom, (status, resp, msgId) => {
		if (status === 'ok') {
			onSuccess(msgId);
		} else {
			Logger.info(`ivcs-js-sdk:send cancel Instruction message: ${data},to ${toWhom} failed,reason:${resp},msgId ${msgId},${new Date().toISOString()}`);
			dealWithCcsSendMessageResponse(status, resp, onFailure);
			// let response = new IvcsError('5012', resp);
			// onFailure(response);
		}
	});
}

/**
 * 监听收到取消事件指令
 * @param {object} ccsClient ccsClient对象
 * @param {function} ack 回调，表明收到取消事件指令
 */
export function onCancelInstruction(ccsClient, ack) {
	let isValid = validateCcsClient(ccsClient);
	if (isValid != 'ok') {
		Logger.warning(`ivcs-js-sdk:onCancelInstruction error,${JSON.stringify(isValid)}`);
		return;
	}
	ccsClient.onCancelInstruction(data => {
		ack(data);
	});
}

/**
 * 处理ccs发送消息的失败情况下的返回值
 * @param {string} status disconnected/timeout/ack-timeout/error
 * @param {string} resp
 * @param {function} onFailure
 * @returns IvcsError
 */
export function dealWithCcsSendMessageResponse(status, resp, onFailure) {
	let response;
	if (status === 'disconnected') {
		response = new IvcsError(ivcsErrorCodeEnum.ERROR_CCS_SENDMESSAGE_DISCONNECTED, resp);
	} else if (status === 'timeout') {
		response = new IvcsError(ivcsErrorCodeEnum.ERROR_CCS_SENDMESSAGE_TIMEOUT, resp);
	} else if (status === 'ack-timeout') {
		response = new IvcsError(ivcsErrorCodeEnum.ERROR_CCS_SENDMESSAGE_ACKTIMEOUT, resp);
	} else {
		response = new IvcsError(ivcsErrorCodeEnum.ERROR_CCS_SESSION_MESSAGE_FAILED, resp);
	}
	onFailure && onFailure(response);
	return response;
}
